package com.study.el;

import org.junit.*;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.*;

/**
 * 作用描述
 * 参考 https://www.cnblogs.com/ClassNotFoundException/p/6000018.html
 *
 * @author doveylovey
 * @version v1.0.0
 * @email 1135782208@qq.com
 * @date 2024年05月05日
 */
public class SpringElTests02 {
    // 指定 SpelExpressionParser 解析器实现类
    ExpressionParser parser = null;

    @Before
    public void setup() {
        parser = new SpelExpressionParser();
    }

    @Test
    public void testString01() {
        String str1 = parser.parseExpression("'Hello World!'").getValue(String.class);
        System.out.println("str1=" + str1);

        String str2 = parser.parseExpression("\"Hello World!\"").getValue(String.class);
        System.out.println("str2=" + str2);

        // 字符串连接及截取表达式：使用“+”进行字符串连接，使用“'String'[0] [index]”来截取一个字符，目前只支持截取一个，
        String str3 = parser.parseExpression("'Hello ' + 'World!'").getValue(String.class);
        System.out.println("str3=" + str3);

        String str4 = parser.parseExpression("'Hello World!'[0]").getValue(String.class);
        System.out.println("str4=" + str4);
    }

    @Test
    public void testNumber01() {
        int int1 = parser.parseExpression("1").getValue(Integer.class);
        System.out.println("int1=" + int1);

        long long1 = parser.parseExpression("-1L").getValue(long.class);
        System.out.println("long1=" + long1);

        float float1 = parser.parseExpression("1.1").getValue(Float.class);
        System.out.println("float1=" + float1);

        double double1 = parser.parseExpression("1.1E+2").getValue(double.class);
        System.out.println("double1=" + double1);

        int hex1 = parser.parseExpression("0xa").getValue(Integer.class);
        System.out.println("hex1=" + hex1);

        long hex2 = parser.parseExpression("0xaL").getValue(long.class);
        System.out.println("hex2=" + hex2);
    }

    @Test
    public void testBoolean01() {
        boolean true1 = parser.parseExpression("true").getValue(boolean.class);
        System.out.println("true1=" + true1);

        boolean false1 = parser.parseExpression("false").getValue(boolean.class);
        System.out.println("false1=" + false1);
    }

    @Test
    public void testNull01() {
        Object null1 = parser.parseExpression("null").getValue(Object.class);
        System.out.println("null1=" + null1);
    }

    @Test
    public void testArithmetic01() {
        // SpEL支持加(+)、减(-)、乘(*)、除(/)、求余(%)、幂(^)运算
        // SpEL还提供除(div)和求余(mod)两个额外的运算符，与%和/等价，且不区分大小写
        int result10 = parser.parseExpression("1+2-3*4/2").getValue(Integer.class);
        System.out.println("加减乘除=" + result10);
        int result11 = parser.parseExpression("12/2").getValue(Integer.class);
        System.out.println("除法1=" + result11);
        int result12 = parser.parseExpression("12 div 2").getValue(Integer.class);
        System.out.println("除法2=" + result12);

        int result20 = parser.parseExpression("4%3").getValue(Integer.class);
        System.out.println("求余1=" + result20);
        int result21 = parser.parseExpression("4 mod 3").getValue(Integer.class);
        System.out.println("求余2=" + result21);

        int result30 = parser.parseExpression("2^3").getValue(Integer.class);
        System.out.println("幂运算=" + result30);
    }

    @Test
    public void testRelation01() {
        // SpEL支持等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=)、区间(between)运算
        // SpEL还提供了等价的等于(EQ) 、不等于(NE)、 大于(GT)、大于等于(GE)、 小于(LT)、小于等于(LE)运算符，且不区分大小写
        Boolean result10 = parser.parseExpression("1>2").getValue(boolean.class);
        System.out.println("大于运算1=" + result10);
        Boolean result11 = parser.parseExpression("1 GT 2").getValue(boolean.class);
        System.out.println("大于运算2=" + result11);

        Boolean result20 = parser.parseExpression("1 between {1, 2}").getValue(boolean.class);
        System.out.println("区间运算=" + result20);
    }

    @Test
    public void testLogic01() {
        // SpEL支持且(and)、或(or)、非(!、NOT)逻辑运算。注：逻辑运算符不支持Java中的&&和||
        boolean result10 = parser.parseExpression("2>1 and (!true or !false)").getValue(boolean.class);
        System.out.println("逻辑运算=" + result10);

        boolean result20 = parser.parseExpression("2>1 and (NOT true or NOT false)").getValue(boolean.class);
        System.out.println("逻辑运算=" + result20);
    }

    @Test
    public void testThreeEyes01() {
        // SpEL支持三目运算及Elivis运算表达式
        // 三目运算符：表达式1?表达式2:表达式3，用于构造三目运算表达式
        boolean result10 = parser.parseExpression("2>1?true:false").getValue(boolean.class);
        System.out.println("三目运算符=" + result10);

        // Elivis运算符：表达式1?:表达式2，从Groovy语言中引入用于简化三目运算符，当表达式1为非null时则返回表达式1，当表达式1为null时则返回表达式2
        boolean result20 = parser.parseExpression("null?:false").getValue(boolean.class);
        System.out.println("Elivis运算符1=" + result20);
        boolean result21 = parser.parseExpression("true?:false").getValue(boolean.class);
        System.out.println("Elivis运算符2=" + result21);
    }

    @Test
    public void testRegular01() {
        // SpEL支持正则表达式，格式为：str matches regex
        boolean result10 = parser.parseExpression("'123' matches '\\d{3}'").getValue(boolean.class);
        System.out.println("正则表达式=" + result10);
    }

    @Test
    public void testClass01() {
        // 类类型表达式：使用T(Type)来表示java.lang.Class实例，Type必须是类全限定名，java.lang包除外，即该包下的类可以不指定包名；使用类类型表达式还可以进行访问类静态方法及类静态字段

        // java.lang包类访问
        Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);
        Assert.assertEquals(String.class, result1);
        System.out.println("result1=" + result1);

        // 其他包类访问：必须是类全限定名
        Class<String> result2 = parser.parseExpression("T(com.study.el.SpringElTests02)").getValue(Class.class);
        Assert.assertEquals(SpringElTests02.class, result2);
        System.out.println("result2=" + result2);

        // 类静态字段访问
        int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
        Assert.assertEquals(Integer.MAX_VALUE, result3);
        System.out.println("result3=" + result3);

        // 类静态方法调用
        int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);
        Assert.assertEquals(1, result4);
        System.out.println("result4=" + result4);
    }

    @Test
    public void testClass02() {
        // 类实例化：同样使用java关键字new，类名必须是全限定名，但java.lang包内的类型除外，如String、Integer
        String result1 = parser.parseExpression("new String('haha')").getValue(String.class);
        Assert.assertEquals("haha", result1);
        System.out.println("result1=" + result1);

        Date result2 = parser.parseExpression("new java.util.Date()").getValue(Date.class);
        Assert.assertNotNull(result2);
        System.out.println("result2=" + result2);
    }

    @Test
    public void testClass03() {
        // instanceof表达式：SpEL支持instanceof运算符，跟Java内使用同义；如“'haha' instanceof T(String)”将返回true
        boolean result1 = parser.parseExpression("'haha' instanceof T(String)").getValue(boolean.class);
        Assert.assertTrue(result1);
        System.out.println("result1=" + result1);
    }

    @Test
    public void testClass04() {
        // 变量定义及引用：变量定义通过EvaluationContext接口的setVariable(variableName, value)方法定义；在表达式中使用“#variableName”引用；
        // 除了引用自定义变量，SpE还允许引用根对象及当前上下文对象，使用“#root”引用根对象，使用“#this”引用当前上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        context.setVariable("variable", "haha");
        context.setVariable("variable", "haha");
        String result1 = parser.parseExpression("#variable").getValue(context, String.class);
        Assert.assertEquals("haha", result1);
        System.out.println("result1=" + result1);

        context = new StandardEvaluationContext("haha");
        String result2 = parser.parseExpression("#root").getValue(context, String.class);
        Assert.assertEquals("haha", result2);
        System.out.println("result2=" + result2);

        String result3 = parser.parseExpression("#this").getValue(context, String.class);
        Assert.assertEquals("haha", result3);
        System.out.println("result3=" + result3);
    }

    @Test
    public void testClass05() throws NoSuchMethodException {
        // 自定义函数：目前只支持类静态方法注册为自定义函数，SpEL使用StandardEvaluationContext的registerFunction()方法进行注册自定义函数，其实完全可以使用setVariable代替，两者其实本质是一样的
        StandardEvaluationContext context = new StandardEvaluationContext();
        Method parseInt = Integer.class.getDeclaredMethod("parseInt", String.class);
        context.registerFunction("parseInt", parseInt);
        context.setVariable("parseInt2", parseInt);
        boolean result1 = parser.parseExpression("#parseInt('3') == #parseInt2('3')").getValue(context, boolean.class);
        Assert.assertEquals(true, result1);
        System.out.println("result1=" + result1);
    }

    @Test
    public void testClass06() {
        // 赋值表达式：SpEL既允许给自定义变量赋值，也允许给对象赋值，直接使用“#variableName=value”即可赋值
        // 1、给root对象赋值
        EvaluationContext context = new StandardEvaluationContext("aaaa");
        // 使用“#root='aaaaa'”给根对象赋值
        String result1 = parser.parseExpression("#root='aaaaa'").getValue(context, String.class);
        Assert.assertEquals("aaaaa", result1);
        System.out.println("result1=" + result1);
        // 使用“"#this='aaaa'”给当前上下文对象赋值
        String result2 = parser.parseExpression("#this='aaaa'").getValue(context, String.class);
        Assert.assertEquals("aaaa", result2);
        System.out.println("result2=" + result2);
        // 2、给自定义变量赋值
        context.setVariable("#variable", "variable");
        // 使用“#variable=#root”给自定义变量赋值
        String result3 = parser.parseExpression("#variable=#root").getValue(context, String.class);
        Assert.assertEquals("aaaa", result3);
        System.out.println("result3=" + result3);
    }

    @Test
    public void testClass07() {
        // 对象属性存取及安全导航表达式：对象属性获取非常简单，使用如“a.property.property”这种点缀式获取，SpEL对于属性名首字母是不区分大小写的；
        // SpEL还引入了Groovy语言中的安全导航运算符“(对象|属性)?.属性”，用来避免但“?.”前边的表达式为null时抛出空指针异常，而是返回null；
        // 修改对象属性值则可以通过赋值表达式或Expression接口的setValue方法修改
        // 1、访问root对象属性
        Date date = new Date();
        StandardEvaluationContext context = new StandardEvaluationContext(date);
        int result1 = parser.parseExpression("Year").getValue(context, int.class);
        Assert.assertEquals(date.getYear(), result1);
        System.out.println("result1=" + result1);

        int result2 = parser.parseExpression("year").getValue(context, int.class);
        Assert.assertEquals(date.getYear(), result2);
        System.out.println("result2=" + result2);

        // 对于当前上下文对象属性及方法访问，可以直接使用属性或方法名访问，比如此处根对象date属性“year”，注意此处属性名首字母不区分大小写
        // 2、安全访问
        context.setRootObject(null);
        Object result3 = parser.parseExpression("#root?.year").getValue(context, Object.class);
        Assert.assertNull(result3);
        System.out.println("result3=" + result3);

        // SpEL引入了Groovy的安全导航运算符，比如此处根对象为null，所以如果访问其属性时肯定抛出空指针异常，而采用“?.”安全访问导航运算符将不抛空指针异常，而是简单的返回null
        // 3、给root对象属性赋值：给对象属性赋值可以采用赋值表达式或Expression接口的setValue方法赋值，而且也可以采用点缀方式赋值
        context.setRootObject(date);
        int result4 = parser.parseExpression("Year = 4").getValue(context, int.class);
        Assert.assertEquals(4, result4);
        System.out.println("result4=" + result4);

        parser.parseExpression("Year").setValue(context, 5);
        int result5 = parser.parseExpression("Year").getValue(context, int.class);
        Assert.assertEquals(5, result5);
        System.out.println("result5=" + result5);
    }

    @Test
    public void testClass08() {
        // 对象方法调用：跟Java语法一样，如“'haha'.substring(2, 4)”将返回“ha”，而对于根对象可以直接调用方法
        Date date = new Date();
        StandardEvaluationContext context = new StandardEvaluationContext(date);
        // 根对象date方法“getYear”可以直接调用
        int result2 = parser.parseExpression("getYear()").getValue(context, int.class);
        Assert.assertEquals(date.getYear(), result2);
        System.out.println("result2=" + result2);
    }

    @Test
    public void testClass09() {
        // Bean引用：SpEL支持使用@符号来引用Bean，在引用Bean时需要使用BeanResolver接口实现来查找Bean，Spring提供BeanFactoryResolver实现
        // 在该示例中，首先初始化了一个IoC容器，ClassPathXmlApplicationContext实现默认会把System.getProperties()注册为systemProperties类型的Bean，因此可以使用@systemProperties来引用该Bean
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
        ctx.refresh();
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setBeanResolver(new BeanFactoryResolver(ctx));
        Properties result1 = parser.parseExpression("@systemProperties").getValue(context, Properties.class);
        Assert.assertEquals(System.getProperties(), result1);
        System.out.println("result1=" + result1);
    }

    @Test
    public void testCollection01() {
        // 内联List：从Spring3.0.4开始支持内联List，使用{表达式，……}定义内联List，如{1,2,3}将返回一个整型的ArrayList，而{}将返回空的List，
        // 对于字面量表达式列表，SpEL会使用java.util.Collections.unmodifiableList方法将列表设置为不可修改
        // 将返回不可修改的空List
        List<Integer> result1 = parser.parseExpression("{}").getValue(List.class);
        System.out.println("result1=" + result1);

        // 对于字面量列表也将返回不可修改的List
        List<Integer> result2 = parser.parseExpression("{1,2,3}").getValue(List.class);
        Assert.assertEquals(new Integer(1), result2.get(0));
        try {
            result2.set(0, 2);
            // 不可能执行到这，对于字面量列表不可修改
            Assert.fail();
        } catch (Exception e) {
        }
        System.out.println("result2=" + result2);

        // 对于列表中只要有一个不是字面量表达式，将只返回原始List，不会进行不可修改处理
        String expression3 = "{{1+2,2+4},{3,4+4}}";
        List<List<Integer>> result3 = parser.parseExpression(expression3).getValue(List.class);
        result3.get(0).set(0, 1);
        Assert.assertEquals(2, result3.size());
        System.out.println("result3=" + result3);

        // 声明一个大小为2的一维数组并初始化
        int[] result4 = parser.parseExpression("new int[2]{1,2}").getValue(int[].class);
        System.out.println("result4=" + result4);

        // 定义一维数组但不初始化
        int[] result5 = parser.parseExpression("new int[1]").getValue(int[].class);
        System.out.println("result5=" + result5);
    }

    @Test
    public void testCollection02() {
        // 内联数组：和Java数组定义类似，只是在定义时进行多维数组初始化
        // 定义多维数组但不初始化
        int[][][] result1 = parser.parseExpression("new int[1][2][3]").getValue(int[][][].class);
        System.out.println("result1=" + result1);

        // 错误的定义多维数组，多维数组不能初始化
        int[][][] result4 = null;
        try {
            result4 = parser.parseExpression("new int[1][2][3]{{1}{2}{3}}").getValue(int[][][].class);
            Assert.fail();
        } catch (Exception e) {
        }
        System.out.println("result4=" + result4);
    }

    @Test
    public void testCollection03() {
        // 集合，字典元素访问：SpEL目前支持所有集合类型和字典类型的元素访问，使用集合[索引]访问集合元素，使用map[key]访问字典元素
        // 注：集合元素访问是通过Iterator遍历来定位元素位置的

        // SpEL内联List访问
        int result1 = parser.parseExpression("{1,2,3}[0]").getValue(int.class);
        // 即list.get(0)
        Assert.assertEquals(1, result1);
        System.out.println("result1=" + result1);

        // SpEL目前支持所有集合类型的访问
        Collection<Integer> collection = new HashSet<>();
        collection.add(1);
        collection.add(2);
        EvaluationContext context2 = new StandardEvaluationContext();
        context2.setVariable("collection", collection);
        int result2 = parser.parseExpression("#collection[1]").getValue(context2, int.class);
        // 对于任何集合类型通过Iterator来定位元素
        Assert.assertEquals(2, result2);
        System.out.println("result2=" + result2);

        // SpEL对Map字典元素访问的支持
        Map<String, Integer> map = new HashMap<String, Integer>() {{
            this.put("a", 1);
        }};
        EvaluationContext context3 = new StandardEvaluationContext();
        context3.setVariable("map", map);
        int result3 = parser.parseExpression("#map['a']").getValue(context3, int.class);
        Assert.assertEquals(1, result3);
        System.out.println("result3=" + result3);
    }

    @Test
    public void testCollection04() {
        // 列表，字典，数组元素修改：可以使用赋值表达式或Expression接口的setValue方法修改
        // 对数组修改直接对“#array[index]”赋值即可修改元素值，同理适用于集合和字典类型

        // 1、修改数组元素值
        int[] array = new int[]{1, 2};
        EvaluationContext context1 = new StandardEvaluationContext();
        context1.setVariable("array", array);
        int result1 = parser.parseExpression("#array[1] = 3").getValue(context1, int.class);
        Assert.assertEquals(3, result1);
        System.out.println("result1=" + result1);

        // 2、修改集合值
        Collection<Integer> collection = new ArrayList<Integer>() {{
            this.add(1);
            this.add(2);
        }};
        EvaluationContext context2 = new StandardEvaluationContext();
        context2.setVariable("collection", collection);
        int result2 = parser.parseExpression("#collection[1] = 3").getValue(context2, int.class);
        Assert.assertEquals(3, result2);
        System.out.println("result2=" + result2);

        parser.parseExpression("#collection[1]").setValue(context2, 4);
        result2 = parser.parseExpression("#collection[1]").getValue(context2, int.class);
        Assert.assertEquals(4, result2);
        System.out.println("result2=" + result2);

        // 3、修改map元素值
        Map<String, Integer> map = new HashMap<String, Integer>() {{
            this.put("a", 1);
        }};
        EvaluationContext context3 = new StandardEvaluationContext();
        context3.setVariable("map", map);
        int result3 = parser.parseExpression("#map['a'] = 2").getValue(context3, int.class);
        Assert.assertEquals(2, result3);
        System.out.println("result3=" + result3);
    }

    @Test
    public void testCollection05() {
        // 集合投影：在SQL中投影指从表中选择出列，而在SpEL指根据集合中的元素中通过选择来构造另一个集合，该集合和原集合具有相同数量的元素；SpEL使用“（list|map）.![投影表达式]”来进行投影运算
        // 1.首先准备测试数据
        Collection<Integer> collection = new ArrayList<Integer>() {{
            this.add(4);
            this.add(5);
        }};
        Map<String, Integer> map = new HashMap<String, Integer>() {{
            this.put("a", 1);
            this.put("b", 2);
        }};
        // 2.测试集合或数组
        EvaluationContext context1 = new StandardEvaluationContext();
        context1.setVariable("collection", collection);
        Collection<Integer> result1 = parser.parseExpression("#collection.![#this+1]").getValue(context1, Collection.class);
        Assert.assertEquals(2, result1.size());
        Assert.assertEquals(new Integer(5), result1.iterator().next());
        // 对于集合或数组使用如上表达式进行投影运算，其中投影表达式中“#this”代表每个集合或数组元素，可以使用比如“#this.property”来获取集合元素的属性，其中“#this”可以省略
        // 3.测试字典
        EvaluationContext context2 = new StandardEvaluationContext();
        context2.setVariable("map", map);
        List<Integer> result2 = parser.parseExpression("#map.![ value+1]").getValue(context2, List.class);
        Assert.assertEquals(2, result2.size());
        // SpEL投影运算还支持Map投影，但Map投影最终只能得到List结果，如上所示，对于投影表达式中的“#this”将是Map.Entry，所以可以使用“value”来获取值，使用“key”来获取键
    }

    @Test
    public void testCollection06() {
        // 集合选择：在SQL中指使用select进行选择行数据，而在SpEL指根据原集合通过条件表达式选择出满足条件的元素并构造为新的集合，SpEL使用“(list|map).?[选择表达式]”，其中选择表达式结果必须是boolean类型，如果true则选择的元素将添加到新集合中，false将不添加到新集合中
        // 1.首先准备测试数据
        Collection<Integer> collection = new ArrayList<Integer>() {{
            this.add(4);
            this.add(5);
        }};
        Map<String, Integer> map = new HashMap<String, Integer>() {{
            this.put("a", 1);
            this.put("b", 2);
        }};
        // 2.集合或数组测试
        EvaluationContext context1 = new StandardEvaluationContext();
        context1.setVariable("collection", collection);
        Collection<Integer> result1 = parser.parseExpression("#collection.?[#this>4]").getValue(context1, Collection.class);
        Assert.assertEquals(1, result1.size());
        Assert.assertEquals(new Integer(5), result1.iterator().next());
        // 对于集合或数组选择，如“#collection.?[#this>4]”将选择出集合元素值大于4的所有元素。选择表达式必须返回布尔类型，使用“#this”表示当前元素
        // 3.字典测试
        EvaluationContext context2 = new StandardEvaluationContext();
        context2.setVariable("map", map);
        Map<String, Integer> result2 = parser.parseExpression("#map.?[#this.key != 'a']").getValue(context2, Map.class);
        Assert.assertEquals(1, result2.size());

        List<Integer> result3 = parser.parseExpression("#map.?[key != 'a'].![value+1]").getValue(context2, List.class);
        Assert.assertEquals(new Integer(3), result3.iterator().next());

        // 对于字典选择，如“#map.?[#this.key != 'a']”将选择键值不等于”a”的，其中选择表达式中“#this”是Map.Entry类型，而最终结果还是Map，这点和投影不同；集合选择和投影可以一起使用，如“#map.?[key != 'a'].![value+1]”将首先选择键值不等于”a”的，然后在选出的Map中再进行“value+1”的投影
    }
}
